﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: PersistingTrace.cs
//
//  Description: Forward-only cursor for objects from a data source.
// 
//--------------------------------------------------------------------------

using System;
using System.Data;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Serialization;
using Chili.Opf3.Storages;
using Chili.Opf3.Concurrency;
using Chili.Opf3.Mapping;
using Chili.Opf3.Caching;

namespace Chili.Opf3
{
	/// <summary>
	/// Provides a way of reading a forward-only stream of objects from a data source.
	/// </summary>
	/// <remarks>
	/// <para>
	/// To create an ObjectReader you must call the 
	/// <see cref="Opf3.ObjectContext.GetObjectReader{T}(Opf3.Query.IQuery)">GetObjectReader</see> method of the
	/// <see cref="Opf3.ObjectContext">ObjectContext</see> rather than directly using a 
	/// constructor. 
	/// </para>
	/// <para>
	/// While the ObjectReader is in use and a transaction is opened on the associated
	/// <see cref="Opf3.ObjectContext">ObjectContext</see> the context is blocked and can not
	/// serve any other purpose until the ObjectReader is closed. If no transaction is opened
	/// the <see cref="Opf3.ObjectContext">ObjectContext</see> is free to serve any other request.
	/// Each opened ObjectReader should be closed immediately after having finished using it. 
	/// It is recommended to use in CS the "using" statement when working with the ObjectReader or 
	/// call immediately <see cref="Opf3.ObjectReader{T}.Close">Close</see> after having retrieved 
	/// the required data (objects).
	/// </para>
	/// <para>
	/// Changes made to a resultset by another process or thread while data is being read may be visible 
	/// to the user of the ObjectReader. However, the precise behavior is timing dependent.
	/// </para>
	/// <para>
	/// <see cref="Opf3.ObjectReader{T}.IsClosed">IsClosed</see>, 
	/// <see cref="Opf3.ObjectReader{T}.HasObjects">HasObjects</see> and 
	/// <see cref="Opf3.ObjectReader{T}.FinishedReading">FinishedReading</see> are the only properties that
	/// can be called after closing the ObjectReader.
	/// </para>
	/// </remarks>
	/// <example>
	///		<code lang="CS">
	/// [Persistent("USER")]
	/// public class User
	/// {
	///		private string _name = null;
	/// 
	///		[Field("NAME")]
	///		public string Name
	///		{
	///			get { return _name; }
	///			set { _name = value; }
	///		}
	/// 
	///		// ... Other properties and fields.
	/// }
	/// 
	/// // ... Other code
	/// 
	/// List&lt;User&gt; userList = new List&lt;User&gt;();
	/// // Loads an ObjectReader of User from the storage and sorts the result set.
	/// using(ObjectReader&lt;User&gt; reader = context.GetObjectReader&lt;User&gt;
	///		("UserName like {0} SortBy UserName Asc", "%mit%"))
	/// {
	///		// Loop until we get objects.
	///		while (reader.Read())
	///		{
	///			User user = reader.Current;
	///			
	///			if (user.Name == "Smith")
	///				userList.Add(user);
	///		}
	/// 
	///		// ObjectReader supports also foreach.
	///		// foreach(User user in reader)
	///		// {
	///		//		// .. do something.
	///		// }
	/// }
	///		</code>	
	/// </example>
	[Serializable]
	public abstract class ObjectReader<T> : MarshalByRefObject, IEnumerator<T>, IEnumerator, IEnumerable<T>, IEnumerable, IDisposable
	{
		private ObjectContext _context;

		/// <summary>
		/// Creates a new instance of the <see cref="ObjectReader{T}">ObjectReader Class</see>.
		/// This instance is usually required when implementing own caching strategies.
		/// </summary>
		/// <param name="context">The <see cref="ObjectContext">ObjectContext</see> associated with the reader.</param>
		/// <param name="objects">The list of objects enumerated by the reader.</param>
		public static ObjectReader<T> Create(ObjectContext context, IList<T> objects)
		{
			return new CacheObjectReader<T>(context, objects);
		}

		/// <summary>
		/// Creates a new instance of the <see cref="ObjectReader{T}">ObjectReader Class</see>.
		/// This instance is usually required when implementing own storages.
		/// </summary>
		/// <param name="context">The <see cref="ObjectContext">ObjectContext</see> associated with the reader.</param>
		/// <param name="reader">The internal data reader returned by the underlying ADO.NET storage.</param>
		public static ObjectReader<T> Create(ObjectContext context, IDataReader reader)
		{
			return new StorageObjectReader<T>(context, reader);
		}

		/// <summary>
		/// Creates a new instance of the <see cref="Opf3.ObjectReader{T}">ObjectReader Class</see>.
		/// </summary>
		/// <param name="context">The <see cref="Opf3.ObjectContext">ObjectContext</see> associated with the 
		/// ObjectReader. It is required to get the <see cref="Opf3.ObjectInfo">ObjectInfo</see> object
		/// of created objects.</param>
		/// <exception cref="System.ArgumentNullException">Objectcontext is null.</exception>
		internal ObjectReader(ObjectContext context)
		{
			// ObjectContext can't be null.
			if (context == null)
				throw new ArgumentNullException("context");
			_context = context;
		}

		/// <summary>
		/// Event is fired before the data from the storage is used to populate an object.
		/// </summary>
		/// <remarks>
		/// This event is fired before the data from the storage is used to populate an object. It allows the user
		/// to specify another type for the row (item) that is populated with the data. It allows also to have 
		/// certain items ignored. This event can be used in certain scenarios of inheritance.
		/// </remarks>
		/// <example>
		/// The following example introduces to the use of the FecthingStorageRecord event. 
		/// <code lang="CS">
		/// ObjectReader&lt;User&gt; reader = context.GetObjectReader&lt;User&gt;();
		/// // Register the event.
		/// reader.FetchingStorageRecord += new PersistentTypeSelector(MySelection);
		///
		/// // Loop trough all items.
		/// foreach (User user in reader)
		/// {
		///     if (user.GetType() == typeof(User1))
		///         Console.WriteLine("User1 found.");
		///     else
		///         Console.WriteLine("User found.");
		/// }
		/// 
		/// // ...
		/// 
		/// private void MySelection(object sender, FetchingStorageRecordEventArgs e)
		/// {
		///     if (e.DataRecord["Type"] == "1")
		///     {
		///         // Switch the type of the persistent that is created to User1.
		///	        e.Type = typeof(User1);
		///     }
		///     else if ((bool)e.DataRecord["Ignore"])
		///     {
		///         // Tell the framework to ignore this item.
		///         e.Ignore = true;
		///     }
		///     // Have a persistent of type User created.
		///     e.Type = typeof(User);
		/// }
		/// </code>
		/// </example>
		public event PersistentTypeSelector FetchingStorageRecord;

		/// <summary>
		/// Invokes the FetchingStorageRecord event if somebody subscribed.
		/// </summary>
		/// <param name="e">The event args for the event.</param>
		protected virtual void OnFetchingStorageRecord(FetchingStorageRecordEventArgs e)
		{
			if (FetchingStorageRecord != null)
				FetchingStorageRecord(this, e);
		}

		/// <summary>
		/// Returns the associated instance of the <see cref="ObjectContext">ObjectContext</see> class.
		/// </summary>
		protected ObjectContext Context
		{
			get { return _context; }
		}

		/// <summary>
		/// Advances the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> to the next record. 
		/// </summary>
		/// <returns>Returns true if the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> could be
		/// advanced to the next object.</returns>
		public bool Read()
		{
			return Read(1);
		}

		/// <summary>
		/// Advances the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> to one of the next records. 
		/// </summary>
		/// <param name="step">The step that is performed while reading.</param>
		/// <returns>Returns true if the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> could be
		/// advanced to the next object.</returns>
		/// <exception cref="System.ArgumentException">Step is lower then zero.</exception>
		public bool Read(int step)
		{
			if (step < 1)
				throw new ArgumentException("Step is lower then 1. Not allowed.", "step");

			// Do the read of the next object.
			bool result = InternalRead(step);

			// Check if to fire the event.
			if (result)
			{
				T obj = this.Current;
				OnLoaded(new ObjectLoadedEventArgs(obj, _context.GetObjectInfo(obj)));
			}

			// Return the result of the read.
			return result;
		}

		/// <summary>
		/// Event is fired after an object has been loaded from the storage.
		/// </summary>
		internal event EventHandler<ObjectLoadedEventArgs> Loaded;

		/// <summary>
		/// Event is fired before an object has been loaded from the storage.
		/// </summary>
		internal event EventHandler<ObjectLoadingEventArgs> Loading;

		/// <summary>
		/// Invokes the event that is fired before an object has been loaded from the storage.
		/// </summary>
		/// <param name="e">The EventArgs for the event.</param>
		protected virtual void OnLoading(ObjectLoadingEventArgs e)
		{
			if (Loading != null)
				Loading(this, e);
		}

		/// <summary>
		/// Invokes the event that is fired after an object has been loaded from the storage.
		/// </summary>
		/// <param name="e">The EventArgs for the event.</param>
		protected virtual void OnLoaded(ObjectLoadedEventArgs e)
		{
			if (Loaded != null)
				Loaded(this, e);
		}

		/// <summary>
		/// Notifies the <see cref="Opf3.Concurrency.IConcurrencyManager">IConcurrencyManager</see> that 
		/// the object has been loaded.
		/// </summary>
		/// <param name="obj">Object that has been loaded.</param>
		protected void NotifyConcurrencyManager(T obj)
		{
			// Notifiy the concurrency validator.
			if (_context.ConcurrencyManager != null)
				_context.ConcurrencyManager.OnAfterLoad(obj, new ConcurrencyManagerEventArgs(_context.GetObjectInfo(obj), _context));
			else
			{
				// Notify the user that no ConcurrencyManager has been set.
				Tracing.Current.TraceVerbose("ConcurrencyManager is not set.");
			}
		}

		/// <summary>
		/// Advances the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> to one of the next records. 
		/// </summary>
		/// <param name="step">The step that is performed while reading.</param>
		/// <returns>Returns true if the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> could be
		/// advanced to the next object.</returns>
		protected abstract bool InternalRead(int step);

		/// <summary>
		/// Closes the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> object.
		/// </summary>
		public abstract void Close();

		/// <summary>
		/// Indicates whether the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> has been closed. 
		/// </summary>
		/// <value>Returns true if the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> is closed.</value>
		[XmlIgnore]
		public abstract bool IsClosed
		{
			get;
		}

		/// <summary>
		/// Returns whether the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> contains 
		/// one or more rows. 
		/// </summary>
		/// <value>Returns true if the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> has fetched one
		/// ore more objects.</value>
		[XmlIgnore]
		public abstract bool HasObjects
		{
			get; 
		}

		/// <summary>
		/// Returns if the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> has finished reading. 
		/// Is true after Read returned false.
		/// </summary>
		/// <value>Returns true if the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> has finished reading.</value>
		[XmlIgnore]
		public abstract bool FinishedReading
		{
			get;
		}

		/// <summary>
		/// Returns the current object managed by the ObjectReader.
		/// </summary>
		/// <returns></returns>
		protected abstract T GetCurrentObject();

		#region IEnumerator<T> Members

		/// <summary>
		/// Returns the current object. Before accessing this property call at least one time the  
		/// <see cref="Opf3.ObjectReader{T}.Read()">Read</see> method.
		/// </summary>
		/// <exception cref="InvalidOperationException">The <see cref="Opf3.ObjectReader{T}">ObjectReader</see>
		/// has finished reading, has no objects or is closed.</exception>
		/// <value>The current object fetched by the <see cref="Opf3.ObjectReader{T}">ObjectReader</see>.</value>
		/// <exception cref="System.InvalidOperationException">The ObjectReader is not set on a valid item.</exception>
		public T Current
		{
			get 
			{
				// Throw an exception if read hasn't been called or the reader has finished reading.
				if (IsClosed || FinishedReading || !HasObjects)
					throw new InvalidOperationException("Reader is not on a valid item.");
				return this.GetCurrentObject(); 
			}
		}

		/// <summary>
		/// Moves the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> to the next item.
		/// </summary>
		/// <returns>
		/// Returns true if it was possible to move the the next item.
		/// </returns>
		public bool MoveNext()
		{
			return Read(1);
		}

		/// <summary>
		/// Resets the <see cref="Opf3.ObjectReader{T}">ObjectReader</see>. This method is not supported, 
		/// since the <see cref="Opf3.ObjectReader{T}">ObjectReader</see> is forward-only.
		/// </summary>
		/// <exception cref="NotImplementedException">Exception is thrown since the method is not supported.</exception>
		public void Reset()
		{
			throw new NotSupportedException("Object reader is forward-only! Reset is not supported.");
		}

		#endregion

		#region IEnumerable<T> Members

		/// <summary>
		/// Returns an enumerator that iterates through this <see cref="Opf3.ObjectReader{T}">ObjectReader</see>.
		/// </summary>
		public IEnumerator<T> GetEnumerator()
		{
			return this;
		}

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Disposes the <see cref="Opf3.ObjectReader{T}">ObjectReader</see>.
		/// </summary>
		public virtual void Dispose()
		{
			Close();
		}

		#endregion

		#region IEnumerator Members

		/// <summary>
		/// Returns the current object. Before accessing this property call at least one time the  
		/// <see cref="Opf3.ObjectReader{T}.Read()">Read</see> method.
		/// </summary>
		/// <exception cref="InvalidOperationException">The <see cref="Opf3.ObjectReader{T}">ObjectReader</see>
		/// has finished reading, has no objects or is closed.</exception>
		/// <value>The current object fetched by the <see cref="Opf3.ObjectReader{T}">ObjectReader</see>.</value>
		object IEnumerator.Current
		{
			get { return ((IEnumerator<T>)this).Current; }
		}

		#endregion

		#region IEnumerable Members

		/// <summary>
		/// Returns an enumerator that iterates through this <see cref="Opf3.ObjectReader{T}">ObjectReader</see>.
		/// </summary>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return this;
		}

		#endregion
	}
}
